package cn.initcap.controller;

import cn.initcap.common.ErrorCode;
import cn.initcap.common.Result;
import cn.initcap.entity.OrderInfo;
import cn.initcap.entity.SeckillOrder;
import cn.initcap.entity.SeckillUser;
import cn.initcap.rabbitmq.MqSender;
import cn.initcap.rabbitmq.SeckillMessage;
import cn.initcap.redis.GoodsKey;
import cn.initcap.redis.RedisService;
import cn.initcap.service.GoodsService;
import cn.initcap.service.OrderService;
import cn.initcap.service.SeckillService;
import cn.initcap.vo.GoodsVo;
import java.util.HashMap;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @author initcap
 * @date Created in 2018/8/11 PM8:17.
 */
@Slf4j
@Controller
@RequestMapping("/seckill")
public class SeckillFailController implements InitializingBean {

    private GoodsService goodsService;
    private OrderService orderService;
    private SeckillService seckillService;
    private RedisService redisService;
    private MqSender sender;
    private HashMap<Long, Boolean> localOverMap = new HashMap<>();

    @Autowired
    public SeckillFailController(GoodsService goodsService, OrderService orderService, SeckillService seckillService,
                                 RedisService redisService, MqSender sender) {
        this.goodsService = goodsService;
        this.orderService = orderService;
        this.seckillService = seckillService;
        this.redisService = redisService;
        this.sender = sender;
    }

    /**
     * 未经过优化的普通写法(
     * 更新减订单数据库时sql导致超卖update seckill_goods set stock_count = stock_count - 1 where goods_id = #{goodsId}；
     * 还有可能同一个用户秒杀到两个商品)
     * <p>
     * jmeter 2000 * 10
     * QPS 500/sec
     *
     * @param model   模版
     * @param user    用户
     * @param goodsId 商品编号
     */
    @PostMapping(value = "/do_seckill")
    @ResponseBody
    public Result miaosha1(Model model, SeckillUser user,
                           @RequestParam("goodsId") long goodsId) {
        model.addAttribute("user", user);
        if (user == null) {
            return Result.fail(ErrorCode.SESSION_ERROR);
        }
        //判断库存
        GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
        int stock = goods.getStockCount();
        if (stock <= 0) {
            return Result.fail(ErrorCode.SECKILL_OVER);
        }
        //判断是否已经秒杀到了
        SeckillOrder order = orderService.getSeckillOrderByUserIdGoodsId(user.getId(), goodsId);
        if (order != null) {
            return Result.fail(ErrorCode.REPEATE_MIAOSHA);
        }
        //减库存 下订单 写入秒杀订单
        OrderInfo orderInfo = seckillService.seckill(user, goods);
        return Result.success(orderInfo);

    }

    /**
     * 优化了判断是否秒杀到的逻辑，使用的redis进行缓存处理并且解决超卖写法(
     * 将更新减少库存的sql语句上添加判断逻辑 ：
     * update seckill_goods set stock_count = stock_count - 1 where goods_id = #{goodsId} and stock_count > 0；
     * 还有可能同一个用户秒杀到两个商品，解决办法是在seckill_order表中user_id 和goods_id建立唯一索引，即可解决问题)
     * <p>
     * jmeter 2000 * 10
     * QPS 520/sec
     *
     * @param model   模版
     * @param user    用户
     * @param goodsId 商品编号
     */
    @PostMapping(value = "/do_seckill2")
    @ResponseBody
    public Result miaosha2(Model model, SeckillUser user,
                           @RequestParam("goodsId") long goodsId) {
        model.addAttribute("user", user);
        if (user == null) {
            return Result.fail(ErrorCode.SESSION_ERROR);
        }
        //判断库存
        GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
        int stock = goods.getStockCount();
        if (stock <= 0) {
            return Result.fail(ErrorCode.SECKILL_OVER);
        }
        //判断是否已经秒杀到了
        SeckillOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
        if (order != null) {
            return Result.fail(ErrorCode.REPEATE_MIAOSHA);
        }
        //减库存 下订单 写入秒杀订单
        OrderInfo orderInfo = seckillService.miaosha(user, goods);
        return Result.success(orderInfo);

    }

    /**
     * 秒杀优化写法，系统启动时会执行这个方法，将秒杀商品在这个方法中写入缓存
     *
     * @throws Exception 系统初始化异常
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        List<GoodsVo> goodsVoList = goodsService.listGoodsVo();
        if (goodsVoList == null) {
            return;
        }
        for (GoodsVo goodsVo : goodsVoList) {
            log.info("秒杀方法进行初始化，goodsVo:{}", goodsVo);
            redisService.set(GoodsKey.GET_MIAOSHA_GOODS_STOCK, "" + goodsVo.getId(), goodsVo.getStockCount());
            localOverMap.put(goodsVo.getId(), false);
        }
    }

    /**
     * 测试前 首先进入redis 的docker环境，执行redis-cli keys "*moug*" |xargs redis-cli del
     * <p>
     * jmeter 2000 * 10
     * QPS 685.4/sec
     * <p>
     * 加入HashMap内存标记 jmeter 2000 * 10
     * QPS 742.3/sec
     *
     * @param model   模版
     * @param user    用户
     * @param goodsId 商品编号
     */
    @PostMapping(value = "/do_seckill3")
    @ResponseBody
    public Result miaosha3(Model model, SeckillUser user,
                           @RequestParam("goodsId") long goodsId) {
        model.addAttribute("user", user);
        if (user == null) {
            return Result.fail(ErrorCode.SESSION_ERROR);
        }
        // 内存标记，减少redis访问
        boolean over = localOverMap.get(goodsId);
        if (over) {
            return Result.fail(ErrorCode.SECKILL_OVER);
        }
        // 预减库存
        long stock = redisService.decr(GoodsKey.GET_MIAOSHA_GOODS_STOCK, "" + goodsId);
        if (stock < 0) {
            return Result.fail(ErrorCode.SECKILL_OVER);
        }
        // 判断是否已经秒杀到了
        SeckillOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
        if (order != null) {
            return Result.fail(ErrorCode.REPEATE_MIAOSHA);
        }
        // 入队
        SeckillMessage mm = new SeckillMessage();
        mm.setUser(user);
        mm.setGoodsId(goodsId);
        sender.sendMiaoshaMessage(mm);
        //排队中
        return Result.success(0);

    }

    /**
     * orderId：成功
     * -1：秒杀失败
     * 0： 排队中
     */
    @GetMapping(value = "/result")
    @ResponseBody
    public Result miaoshaResult(Model model, SeckillUser user,
                                @RequestParam("goodsId") long goodsId) {
        model.addAttribute("user", user);
        if (user == null) {
            return Result.fail(ErrorCode.SESSION_ERROR);
        }
        long result = seckillService.getMiaoshaResult(user.getId(), goodsId);
        return Result.success(result);
    }

}
